﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Data.OleDb;
using System.Data;
using System.Text;

namespace Shapefile
{
    public class ShapeFile : IDisposable, IEnumerable<ESRIShape>
    {
        // 第一种和第二种数据库在读取某些dbf文件时会引发异常（例如：Downtown1980.dbf）
        //public const string ConnectionStringTemplateJet = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=dBase IV";
        //public const string ConnectionStringTemplateAce = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=dBase IV";

        public const string ConnectionStringTemplateVFPOLEDB = @"Provider=VFPOLEDB.1;Data Source={0};Collating Sequence=MACHINE";

        private const string DbSelectStringTemplate = "SELECT * FROM [{0}]";
        private const string MainPathExtension = "shp";
        private const string IndexPathExtension = "shx";
        private const string DbasePathExtension = "dbf";

        private bool disposed_;
        private bool opened_;
        private bool rawMetadataOnly_;
        private int count_;
        private RectangleD boundingBox_;
        private ShapeType type_;
        private string filename_;
        private string shapefileMainPath_;
        private string shapefileIndexPath_;
        private string shapefileDbasePath_;
        private FileStream mainStream_;
        private FileStream indexStream_;
        private ESRIHeader mainHeader_;
        private ESRIHeader indexHeader_;
        private OleDbConnection dbConnection_;
        private string connectionStringTemplate_;
        private string selectString_;
        private bool is_render_;
        OleDbDataAdapter dataAdapter_;      // 数据命令集到数据源的连接，用于填充DataSet以及更新该数据源
        DataSet ds_;            // 内存中的数据集，用于存储属性数据表
       

        public ShapeFile()
            : this(null, ConnectionStringTemplateVFPOLEDB) { }

        public ShapeFile(string path)
            : this(path, ConnectionStringTemplateVFPOLEDB) { }

        public ShapeFile(string path, string connectionStringTemplate)
        {
            if (connectionStringTemplate == null)
            {
                throw new ArgumentNullException("connectionStringTemplate");
            }

            ConnectionStringTemplate = connectionStringTemplate;

            if (path != null)
            {
                Open(path);
            }
        }

        public void Open(string path)
        {
            if (disposed_)
            {
                throw new ObjectDisposedException("Shapefile is Null!");
            }

            if (path == null)
            {
                throw new ArgumentNullException("path");
            }
            if (path.Length <= 0)
            {
                throw new ArgumentException("path parameter is empty", "path");
            }

            filename_ = Path.GetFileName(path);
            shapefileMainPath_ = Path.ChangeExtension(path, MainPathExtension);
            shapefileIndexPath_ = Path.ChangeExtension(path, IndexPathExtension);
            shapefileDbasePath_ = Path.ChangeExtension(path, DbasePathExtension);

            if (!File.Exists(shapefileMainPath_))
            {
                throw new FileNotFoundException("Shapefile main file not found", shapefileMainPath_);
            }
            if (!File.Exists(shapefileIndexPath_))
            {
                throw new FileNotFoundException("Shapefile index file not found", shapefileIndexPath_);
            }
            if (!File.Exists(shapefileDbasePath_))
            {
                throw new FileNotFoundException("Shapefile dBase file not found", shapefileDbasePath_);
            }

            mainStream_ = File.Open(shapefileMainPath_, FileMode.Open, FileAccess.Read, FileShare.Read);
            indexStream_ = File.Open(shapefileIndexPath_, FileMode.Open, FileAccess.Read, FileShare.Read);

            if (mainStream_.Length < ESRIHeader.HeaderLength)
            {
                throw new InvalidOperationException("Shapefile main file does not contain a valid header");
            }

            if (indexStream_.Length < ESRIHeader.HeaderLength)
            {
                throw new InvalidOperationException("Shapefile index file does not contain a valid header");
            }

            // 读取shp和shx文件及其文件头
            byte[] headerBytes = new byte[ESRIHeader.HeaderLength];
            mainStream_.Read(headerBytes, 0, ESRIHeader.HeaderLength);
            mainHeader_ = new ESRIHeader(headerBytes);
            indexStream_.Read(headerBytes, 0, ESRIHeader.HeaderLength);
            indexHeader_ = new ESRIHeader(headerBytes);

            // 设置对象属性
            type_ = mainHeader_.ShapeType;
            boundingBox_ = new RectangleD(mainHeader_.XMin, mainHeader_.YMax, mainHeader_.XMax, mainHeader_.YMin);

            // index header length is in 16-bit words, including the header - number of 
            // shapes is the number of records (each 4 workds long) after subtracting the header bytes
            count_ = (indexHeader_.FileLength - (ESRIHeader.HeaderLength / 2)) / 4;

            // 打开dbf数据库
            OpenDb();

            opened_ = true;
        }

        public void Close()
        {
            Dispose();
        }

        public void SaveAsMif(string path)
        {
            string mifPath = Path.ChangeExtension(path, "mif");
            string midPath = Path.ChangeExtension(path, "mid");

            StreamWriter MIFStream = new StreamWriter(mifPath, false, Encoding.Default);
            StreamWriter MIDStream = new StreamWriter(midPath, false, Encoding.Default);

            DataTable table = ds_.Tables[0];
            int columns = table.Columns.Count;


            MIFStream.WriteLine("Version 650");
            MIFStream.WriteLine("CHARSET \"WindowsSimpChinese\"");
            MIFStream.WriteLine("DELIMITER \",\"");
            MIFStream.WriteLine("CoordSys NonEarth Units \"m\" Bounds ({0:F4},{1:F4}) ({2:F4},{3:F4})", mainHeader_.XMin, mainHeader_.YMin, mainHeader_.XMax, mainHeader_.YMax);
            MIFStream.WriteLine("Columns {0}", columns);

            for (int i = 0; i < columns; i++)
            {
                string aline;
                switch (table.Columns[i].DataType.Name)
                {
                    case "String":
                    case "Text":
                        aline = "\t" + table.Columns[i].ColumnName + " char(50)";
                        break;
                    case "Decimal":
                        aline = "\t" + table.Columns[i].ColumnName + " decimal(20,4)";
                        break;
                    case "Date":
                    case "DateTime":
                        aline = "\t" + table.Columns[i].ColumnName + " Date";
                        break;
                    default:
                        aline = "\t" + table.Columns[i].ColumnName + " char(250)";
                        break;
                }
                MIFStream.WriteLine(aline);
            }
            MIFStream.WriteLine("DATA\n");

            int totalRecords = 0;

            foreach(var shape in this)
            {
                totalRecords += 1;
                switch (shape.Type)
                {
                    case ShapeType.Point:
                        ESRIPoint shapePoint = shape as ESRIPoint;
                        MIFStream.WriteLine("Point {0} {1}", shapePoint.Point.X, shapePoint.Point.Y);
                        break;

                    case ShapeType.PolyLine:
                        ESRIPolyLine shapePolyLine = shape as ESRIPolyLine;
                        // TODO;
                        break;

                    case ShapeType.Polygon:
                        ESRIPolygon shapePolygon = shape as ESRIPolygon;
                        MIFStream.WriteLine("Region {0}", shapePolygon.Parts.Count);
                        foreach(PointD[] part in shapePolygon.Parts)
                        {
                            MIFStream.WriteLine("{0}", part.Length);
                            foreach(PointD point in part)
                            {
                                MIFStream.WriteLine("{0} {1}", point.X, point.Y);
                            }
                        }
                        break;

                    default:
                        break;
                }
            }

            for(int i = 0; i < totalRecords; i++)
            {
                var row = table.Rows[i];
                bool isFirst = true;
                foreach(DataColumn column in table.Columns)
                {
                    if(isFirst)
                    {
                        isFirst = false;
                        MIDStream.Write("{0}", row[column].ToString().Trim());
                    }
                    else
                    {
                        MIDStream.Write(",{0}", row[column].ToString().Trim());
                    }
                }
                MIDStream.Write("\r\n"); // 每行末尾输出行结束标记
            }

            MIFStream.Close();
            MIDStream.Close();
        }

        public string ConnectionStringTemplate
        {
            get { return connectionStringTemplate_; }
            set { connectionStringTemplate_ = value; }
        }

        public bool RawMetadataOnly
        {
            get { return rawMetadataOnly_; }
            set { rawMetadataOnly_ = value; }
        }

        public bool IsRender
        {
            get { return is_render_; }
            set { is_render_ = value; }
        }

        public int Count
        {
            get
            {
                if (disposed_) throw new ObjectDisposedException("Shapefile");
                if (!opened_) throw new InvalidOperationException("Shapefile not open.");

                return count_;
            }
        }

        public RectangleD BoundingBox
        {
            get
            {
                if (disposed_) throw new ObjectDisposedException("Shapefile");
                if (!opened_) throw new InvalidOperationException("Shapefile not open.");

                return boundingBox_;
            }

        }

        public ShapeType Type
        {
            get
            {
                if (disposed_) throw new ObjectDisposedException("Shapefile");
                if (!opened_) throw new InvalidOperationException("Shapefile not open.");

                return type_;
            }
        }

        public string FileName
        {
            get { return filename_; }
        }

        public DataSet DataSet
        {
            get { return ds_; }
        }

        private void OpenDb()
        {
            string connectionString = string.Format(ConnectionStringTemplate,
                Path.GetDirectoryName(shapefileDbasePath_));
            selectString_ = string.Format(DbSelectStringTemplate,
                Path.GetFileNameWithoutExtension(shapefileDbasePath_));

            dbConnection_ = new OleDbConnection(connectionString);  // 建立连接
            dbConnection_.Open();   // 打开数据库连接

            dataAdapter_ = new OleDbDataAdapter(selectString_, connectionString);

            ds_ = new DataSet();    // 建立一份内存中的数据集
            dataAdapter_.Fill(ds_); // 把之前得到的记录集填充到内存数据集中

        }

        private void CloseDb()
        {
            if (dbConnection_ != null)
            {
                dbConnection_.Close();
                dbConnection_ = null;
            }
        }

        #region IDisposable Members

        ~ShapeFile()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool canDisposeManagedResources)
        {
            if (!disposed_)
            {
                if (canDisposeManagedResources)
                {
                    if (mainStream_ != null)
                    {
                        mainStream_.Close();
                        mainStream_ = null;
                    }

                    if (indexStream_ != null)
                    {
                        indexStream_.Close();
                        indexStream_ = null;
                    }

                    CloseDb();
                }

                disposed_ = true;
                opened_ = false;
            }
        }
        #endregion

        #region IEnumerable Members
        public IEnumerator<ESRIShape> GetEnumerator()
        {

            return new ShapeFileEnumerator(dbConnection_, selectString_, rawMetadataOnly_, mainStream_,
                                          indexStream_, count_);
        }



        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

    }
}
